home *** CD-ROM | disk | FTP | other *** search
/ SuperHack / SuperHack CD.bin / CODING / CPP / JFCPPSYN.ZIP / JFCPPSYN.TXT
Encoding:
Text File  |  1996-03-16  |  12.0 KB  |  447 lines

  1.  
  2.                    PORTABLE THREAD SYNCHRONIZATION USING C++
  3.                                        
  4.    Jim Frost
  5.    Software Tool & Die
  6.    Copyright (c) 1995 Jim Frost
  7.    All Rights Reserved
  8.    Last changed February 17, 1995
  9.    
  10.      _________________________________________________________________
  11.    
  12. Abstract
  13.  
  14.    
  15.    
  16.    This document provides example C++ classes implementing a series of
  17.    synchronization objects useful for building portable multithreaded
  18.    applications.
  19.    
  20.    This is a work in progress. Do not rely on the absolute accuracy of
  21.    the implementations.
  22.      _________________________________________________________________
  23.    
  24. Table Of Contents
  25.  
  26.      * Introduction
  27.      * The Mutex Class
  28.           + Interface
  29.           + Win32 implementation
  30.           + Solaris 2.x implementation
  31.           + Example of usage (protected class)
  32.      * The Semaphore Class
  33.           + Interface
  34.           + Win32 implementation
  35.           + Solaris 2.x implementation
  36.           + Example of usage (queue template class)
  37.      * The Event Class
  38.           + Interface
  39.           + Win32 implementation
  40.           + Solaris 2.x implementation
  41.      * The Gate Class
  42.           + Interface
  43.           + Win32 implementation
  44.           + Solaris 2.x implementation
  45.      * The Readers/Writer Class
  46.           + Generic implementation
  47.           + Solaris 2.x implementation
  48.      * Fair-Share Readers/Writer Class
  49.           + Generic implementation
  50.             
  51.    
  52.      _________________________________________________________________
  53.    
  54. Introduction 
  55.  
  56.    
  57.    
  58.    Thread-aware operating systems are here, and are rapidly growing in
  59.    popularity. Today there are at least three popular multithreaded
  60.    operating systems -- Windows NT, OS/2, and Solaris 2.x -- and a POSIX
  61.    standard, Pthreads, is on the way.
  62.    
  63.    While each of the operating systems provides very similar basic
  64.    synchronization objects, they do so with considerably variant syntax
  65.    and usage. As a result it is not trivial to write multithreaded
  66.    applications which port between the operating systems easily.
  67.    
  68.    This document provides a series of C++ classes which implement both
  69.    basic and more complex synchronization objects, as well as several
  70.    useful data structures that use them. Implementations are provided for
  71.    both Win32 (Windows NT and Windows 95) and Solaris 2.x environments.
  72.      _________________________________________________________________
  73.    
  74. The Mutex Class 
  75.  
  76.    
  77.    
  78.    The simplest synchronization object is the mutex, which is used to
  79.    allow only one thread at a time access to a resource (usually a data
  80.    structure).
  81.    
  82.    The Mutex class implements a recursively lockable mutex, one where a
  83.    single thread may lock the same mutex multiple times. In practice this
  84.    is the safest and most useful form of a mutex.
  85.    
  86.    The interface to the Mutex class is: 
  87.    
  88.  
  89. class Mutex {
  90. public:
  91.   Mutex(void);
  92.   void Lock(void);
  93.   void Unlock(void);
  94. };
  95.  
  96.  
  97.    
  98.    
  99.    See:
  100.      * Win32 implementation
  101.      * Solaris 2.x implementation
  102.        
  103.    
  104.    
  105.    Using the mutex class it is easy to create C++ classes which are
  106.    totally thread-safe. For example: 
  107.    
  108.  
  109. class SafeInteger : private Mutex {
  110. private:
  111.   int value;
  112.  
  113. public:
  114.   void SetValue(int new_value) {
  115.     Lock();
  116.     value = new_value;
  117.     Unlock();
  118.   }
  119.  
  120.   int GetValue(void) {
  121.     Lock();
  122.     int ret_value = value;
  123.     Unlock();
  124.     return ret_value;
  125.   }
  126. };
  127.  
  128.  
  129.    
  130.    
  131.    Because the only access to the data member is through the member
  132.    functions of its class, it is impossible to accidentally access the
  133.    data unsafely.
  134.      _________________________________________________________________
  135.    
  136. The Semaphore Class 
  137.  
  138.    
  139.    
  140.    The semaphore synchronization object is used so that one thread may
  141.    allow one or more other threads to pass a particular point at a
  142.    particular time.
  143.    
  144.    The interface to the Semaphore class is: 
  145.    
  146.  
  147. class Semaphore {
  148. public:
  149.   Semaphore(void);
  150.   Semaphore(int available);
  151.   ~Semaphore(void);
  152.   void Wait(void);
  153.   void Post(void);
  154.   void Post(int how_many);
  155. };
  156.  
  157.  
  158.    
  159.    
  160.    See:
  161.      * Win32 implementation
  162.      * Solaris 2.x implementation
  163.        
  164.    
  165.    
  166.    Semaphores are most useful when threads are in a producer/consumer
  167.    relationship, as in the case of a queue. The following implements a
  168.    queue template class using the Semaphore class: 
  169.    
  170.  
  171. template <class T> class QueueEntry : public T {
  172. public:
  173.   T value;
  174.   QueueEntry<T>* next;
  175.  
  176.   QueueEntry(const T& item_value) {
  177.     value = item_value;
  178.     next = (QueueEntry*)0;
  179.   }
  180. };
  181.  
  182. template <class T> class Queue : private Semaphore : private Mutex {
  183. private:
  184.   QueueEntry<T>* head;
  185.   QueueEntry<T>* tail;
  186.  
  187. public:
  188.   Queue(void) {
  189.     head = tail = (QueueEntry*)0;
  190.   }
  191.  
  192.   void Add(const T& item_value) {
  193.     Lock();
  194.     if (tail == QueueEntry*)0)
  195.       head = tail = new QueueEntry(item_value);
  196.     else {
  197.       tail->next = new QueueEntry(item_value);
  198.       tail = tail->next;
  199.     }
  200.     Unlock();
  201.     Post(); // wake up any waiting threads
  202.   }
  203.  
  204.   T Wait(void) {
  205.     Semaphore::Wait(); // wait for something to show up
  206.     Lock();
  207.     T value = head->value;
  208.     QueueEntry* old = head;
  209.     head = head->next;
  210.     delete old;
  211.     if (head == (QueueEntry*)0)
  212.       tail = (QueueEntry*)0;
  213.     Unlock();
  214.     return value;
  215.   }
  216. };
  217.  
  218.  
  219.    
  220.    
  221.    Any number of threads may wait on a queue, and as data is added to the
  222.    queue they will be woken up one-at-a-time. If no threads are waiting
  223.    when data is added to the queue, any thread coming along at a later
  224.    time will simply pick up with waiting data immediately.
  225.      _________________________________________________________________
  226.    
  227. The Event Class 
  228.  
  229.    
  230.    
  231.    An event synchronization object is used to block one or more threads
  232.    from proceeding until some specified time, at which time they are all
  233.    released simultaneously.
  234.    
  235.    The interface to the Event class is: 
  236.    
  237.  
  238. class Event {
  239. public:
  240.   Event(void);
  241.   ~Event(void);
  242.   void Signal(void);
  243.   void Wait(void);
  244. };
  245.  
  246.  
  247.    
  248.    
  249.    See:
  250.      * Win32 implementation
  251.      * Solaris 2.x implementation
  252.        
  253.    
  254.      _________________________________________________________________
  255.    
  256. The Gate Class 
  257.  
  258.    
  259.    
  260.    A gate is a synchronization object used to either stop all threads
  261.    from proceeding through a point or to allow them all to proceed.
  262.    
  263.    The interface to the Gate class is: 
  264.    
  265.  
  266. class Gate {
  267. public:
  268.   Gate(void);
  269.   ~Gate(void);
  270.   void Open(void);
  271.   void Close(void);
  272.   void Release(void);
  273.   void Wait(void);
  274. };
  275.  
  276.  
  277.    
  278.    
  279.    See:
  280.      * Win32 implementation
  281.      * Solaris 2.x implementation
  282.        
  283.    
  284.      _________________________________________________________________
  285.    
  286. The Readers/Writer Lock Class 
  287.  
  288.    
  289.    
  290.    In many multithreaded applications a data structure may be accessed by
  291.    many threads, but modified only occasionally. Since it is safe for
  292.    many threads to access a structure simultaneously so long as they
  293.    don't change it, a readers/writer lock, which allows only one thread
  294.    to modify a structure but any number of threads to inspect it when no
  295.    modifications are taking place.
  296.    
  297.    Many operating systems supply readers/writer locks as primitives, but
  298.    some do not. The following is a generic implementation of a
  299.    readers/writer lock, with writer-priority, implemented completely on
  300.    top of the classes described in this document. 
  301.    
  302.  
  303. class RWLock : private Mutex {
  304. private:
  305.   Semaphore write_lock;      // used as a one-at-a-time release valve
  306.   Gate read_barrier;         // used to block/wakeup readers
  307.   unsigned int writer_count; // # of writers waiting for or holding the lock
  308.   unsigned int reader_count; // # of readers holding the lock
  309.  
  310. public:
  311.   ReadLock(void) {
  312.     writer_count = 0;
  313.     reader_count = 0;
  314.   }
  315.  
  316.   void ReadLock(void) {
  317.     Mutex::Lock();
  318.  
  319.     // wait until there are no more writers holding the lock
  320.     while (writer_count > 0) {
  321.       Mutex::Unlock();
  322.       read_barrier.Wait();
  323.       Mutex::Lock();
  324.     }
  325.     reader_count++;
  326.     Mutex::Unlock();
  327.   }
  328.  
  329.   void WriteLock(void) {
  330.     Mutex::Lock();
  331.     writer_count++;       // this stops new readers from getting a lock
  332.     write_lock.Wait();    // wait until the write lock is available
  333.     read_barrier.Close(); // give new readers something to wait for
  334.     Mutex::Unlock();
  335.   }
  336.  
  337.   void Unlock(void) {
  338.     Mutex::Lock();
  339.     if (writer_count > 0) {  // we must be a writer
  340.       writer_count--;
  341.       if (writer_count > 0)  // another writer is waiting
  342.         writer_lock.Post();  // let it go
  343.       else
  344.         read_barrier.Open(); // open the floodgates
  345.     }
  346.     else {
  347.       reader_count--;
  348.  
  349.       // if we're the last reader and a writer is waiting, let it go
  350.       if ((reader_count == 0) && (writer_count > 0)) {
  351.         writer_lock.Post();
  352.     }
  353.     Mutex::Unlock();
  354.   }
  355. };
  356.  
  357.  
  358.    Note: The lack of an atomic release-semaphore-and-wait-for-event
  359.    primitive on some systems requires the use of a Gate rather than an
  360.    Event so that the event is not lost between the release of the
  361.    structure lock by the reader(s) and the wait on the event.
  362.    
  363.    Note: Solaris 2.x supplies readers/writer locks as a primitive, making
  364.    the implementation much simpler.
  365.      _________________________________________________________________
  366.    
  367. Fair-Share Readers/Writer Locks 
  368.  
  369.    
  370.    
  371.    In some cases a single data structure will be accessed very often for
  372.    both reading and writing. To avoid starvation of either the readers or
  373.    the writers, a fair-share lock can be used. A generic implementation
  374.    of the FairRWLock follows. 
  375.    
  376.  
  377. class FairRWLock : private Mutex {
  378. private:
  379.   Semaphore access_lock;        // used as a one-at-a-time release valve
  380.   Gate read_barrier;            // used to block/wakeup readers
  381.   unsigned int is_write_lock;   // nonzero if locked for writing
  382.   unsigned int writer_count;    // # of writers waiting for or holding the loc
  383. k
  384.   unsigned int reader_count;    // # of readers holding the lock
  385.   unsigned int readers_waiting; // # of readers waiting for the lock
  386.  
  387. public:
  388.   ReadLock(void) : access_lock(1) {
  389.     is_write_lock = 0;
  390.     writers_waiting = 0;
  391.     reader_count = 0;
  392.     readers_waiting = 0;
  393.   }
  394.  
  395.   void ReadLock(void) {
  396.     Mutex::Lock();
  397.  
  398.     // if there is at least one writer using the lock or waiting for it,
  399.     // we need to wait for access
  400.     if (writer_count > 0)) {
  401.       if (readers_waiting++ == 0) // if we're the first reader in line
  402.         Mutex::Unlock();
  403.         access_lock.Wait();       // get the access lock
  404.         Mutex::Lock();
  405.         if (readers_waiting > 1)  // then if there are other readers
  406.           read_barrier.Open();    // let them go
  407.       }
  408.       else {
  409.         Mutex::Unlock();
  410.         read_barrier.Wait();      // otherwise wait until someone lets us go
  411.         Mutex::Lock();
  412.       }
  413.       readers_waiting--;
  414.     }
  415.     reader_count++;
  416.     Mutex::Unlock();
  417.   }
  418.  
  419.   void WriteLock(void) {
  420.     Mutex::Lock();
  421.     writer_count++;       // one more writer
  422.     Mutex::Unlock();
  423.     access_lock.Wait();   // wait until the access lock is available
  424.     Mutex::Lock();
  425.     is_write_lock = 1;    // lock is a write lock
  426.     read_barrier.Close(); // give readers something to wait for
  427.     Mutex::Unlock();
  428.   }
  429.  
  430.   void Unlock(void) {
  431.     Mutex::Lock();
  432.     if (is_write_lock) {          // if this is a write lock
  433.       is_write_lock = 0;          // let it go
  434.       writer_count--;             // one less writer
  435.       access_lock.Post();         // now let someone else have a chance
  436.     }
  437.     else if (--reader_count == 0) // if we're the last reader
  438.       access_lock.Post();         // release the access lock
  439.     Mutex::Unlock();
  440.   }
  441. };
  442.  
  443.  
  444.    Note: The fairness of this implementation depends on the fairness of
  445.    the semaphore implementation.
  446.      _________________________________________________________________
  447.